package it.unica.informatica.enotes.manager;

import android.app.Activity;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

import java.io.File;
import java.util.Arrays;

import it.unica.informatica.enotes.R;
import it.unica.informatica.enotes.notes.AttachmentItem;

/**
 * This class perform all the operation related to the acquisition for multimedia contents
 *
 * @author Mirko Marras
 * @version 1.0
 */
public class AttachmentsAcquisitionManager {

    /* ********************************************************************************************
     *                    CONSTRAINTS FOR FILE EXTENSION, SIZE AND DURATION                       *
     **********************************************************************************************/

   public static final int TOLLERANCE_BYTES = 5000;
   public static final int TOLLERANCE_SECONDS = 1;

   public static final String[] ALLOWED_IMAGE_EXTENSIONS = {"jpg", "jpeg", "bmp", "dip", "tiff", "tif", "png", "gif"};
   public static final long ALLOWED_MAX_IMAGE_BYTE = 2024000;
   public static final String ALLOWED_MAX_IMAGE_MB = "max 2 MB";

   public static final String[] ALLOWED_VIDEO_EXTENSIONS = {"avi", "divx", "flv", "mp4", "mpeg", "ogv", "mpg", "wmv"};
   public static final long ALLOWED_MAX_VIDEO_BYTE = 20024000;
   public static final long ALLOWED_MAX_VIDEO_SECONDS = 180;
   public static final String ALLOWED_MAX_VIDEO_MB = "max 20 MB";
   public static final String ALLOWED_MAX_VIDEO_SEC = "max 3 min";

   public static final String[] ALLOWED_AUDIO_EXTENSIONS = {"m4a", "mp3", "ogg", "wav", "wma"};
   public static final long ALLOWED_MAX_AUDIO_BYTE = 12024000;
   public static final long ALLOWED_MAX_AUDIO_SECONDS = 300;
   public static final String ALLOWED_MAX_AUDIO_MB = "max 12 MB";
   public static final String ALLOWED_MAX_AUDIO_SEC = "max 5 min";

    /* ********************************************************************************************
     *         METHODS UTILITIES TO CHECK FILE REQUIREMENTS AND RETRIEVE FILE INFORMATION         *
     **********************************************************************************************/

   /**
    * Checks if external storage is available for read and write
    *
    * @return True if the external storage is available, false otherwise
    */
   public static boolean isExternalStorageWritable() {
      String state = Environment.getExternalStorageState();
      if (Environment.MEDIA_MOUNTED.equals(state)) {
         return true;
      }
      return false;
   }

   /**
    * Check it the file stored at the passed uri respects the extension requirements based on its content type
    * @param filePath The path of the file to which check extension
    * @param type The content type stored in the file
    * @return True if the file respects the requirements, false otherwise
    */
   private static boolean checkExtension(String filePath, String type) {
      /* Retrieve the file path from the uri and its file extension */
      String extension = filePath.substring(filePath.lastIndexOf(".") + 1);

      /* Returns true if the size and the extension of the file match the allowed params */
      if (type.equals(AttachmentItem.TYPE_AUDIO)) {
         return Arrays.asList(ALLOWED_AUDIO_EXTENSIONS).contains(extension);
      }

      if (type.equals(AttachmentItem.TYPE_IMAGE)) {
         return Arrays.asList(ALLOWED_IMAGE_EXTENSIONS).contains(extension);
      }

      if (type.equals(AttachmentItem.TYPE_VIDEO)) {
         return Arrays.asList(ALLOWED_VIDEO_EXTENSIONS).contains(extension);
      }

      return true;
   }

   /**
    * Check it the file stored at the passed uri respects the duration requirements based on its content type
    * @param filePath The path of the file to which check duration
    * @param type The content type stored in the file
    * @return True if the file respects the requirements, false otherwise
    */
   private static boolean checkDuration(String filePath, String type) {
      /* Returns true if the size and the extension of the file match the allowed params */
      if (type.equals(AttachmentItem.TYPE_AUDIO) && checkExtension(filePath, type)) {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         retriever.setDataSource(filePath);
         String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
         long duration = Long.parseLong(time) / 1000;
         return duration <= ALLOWED_MAX_AUDIO_SECONDS + TOLLERANCE_SECONDS;
      }

      if (type.equals(AttachmentItem.TYPE_IMAGE) && checkExtension(filePath, type)) {
         return true;
      }

      if (type.equals(AttachmentItem.TYPE_VIDEO) && checkExtension(filePath, type)) {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         retriever.setDataSource(filePath);
         String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
         long duration = Long.parseLong(time) / 1000;
         return duration <= ALLOWED_MAX_VIDEO_SECONDS + TOLLERANCE_SECONDS;
      }

      return true;
   }

   /**
    * Check it the file stored at the passed uri respects the size requirements based on its content type
    * @param size The size of the file to check
    * @param type The content type stored in the file
    * @return True if the file respects the requirements, false otherwise
    */
   private static boolean checkSize(long size, String type) {
      switch (type) {
         case AttachmentItem.TYPE_AUDIO:
            return size <= ALLOWED_MAX_AUDIO_BYTE + TOLLERANCE_BYTES;
         case AttachmentItem.TYPE_IMAGE:
            return size <= ALLOWED_MAX_IMAGE_BYTE + TOLLERANCE_BYTES;
         case AttachmentItem.TYPE_VIDEO:
            return size <= ALLOWED_MAX_VIDEO_BYTE + TOLLERANCE_BYTES;
         default:
      }
      return true;
   }

   /**
    * Get the absolute path of the file stored at the uri passed as parameter
    *
    * @param uri      The uri of the file to retrieve
    * @param activity The activity that manages the file
    * @return The absolute path of the file stored at the uri
    */
   public static String getPath(Uri uri, Activity activity) {
      Cursor cursor = activity.managedQuery(uri, null, null, null, null);
      cursor.moveToFirst();
      return cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));
   }

    /* ********************************************************************************************
     *           METHODS TO CREATE ATTACHMENTS FROM FILES CREATED/PICKED BY THE USER              *
     **********************************************************************************************/

   /**
    * Create a new attachment object based on the content of the temporary file created by the user
    *
    * @param activity The activity that manages the file
    * @param tempName The name of the temporary file created by the user
    * @param type     The type of the content stored into the file
    * @return A new attachment object that contains the data of the temporary file
    */
   public static AttachmentItem getAttachmentFromFile(Activity activity, Long noteId, String tempName, String type) {
      /* This AttachmentItem will store the decode of the attachment file */
      AttachmentItem result = new AttachmentItem();
      Bitmap thumbnails = null;

      /* Retrieve the file created from an external application and that contains the desired attachment */
      File f = new File(Environment.getExternalStorageDirectory().toString());
      for (File temp : f.listFiles()) {
         if (temp.getName().equals(tempName)) {
            f = temp;
            break;
         }
      }

      /* Verify if the file respect the allowed max file size */
      if (checkSize(f.length(), type) && checkExtension(f.getName(), type) && checkDuration(f.getAbsolutePath(), type)) {

         /* Set the allowed max file size and the thumbnails of the attachment based on its type */
         switch (type) {
            case AttachmentItem.TYPE_AUDIO:
               thumbnails = BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_action_audio);
               break;
            case AttachmentItem.TYPE_IMAGE:
               thumbnails = AttachmentsAcquisitionManager.decodeSampledBitmapFromFile(f, 50, 50);
               break;
            case AttachmentItem.TYPE_VIDEO:
               thumbnails = ThumbnailUtils.createVideoThumbnail(f.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
               break;
            default:
         }

         result.setName(AttachmentsStoringManager.copyToAppMemory(f, noteId));
         result.setType(type);
         result.setThumbnails(thumbnails);

      } else if (!checkSize(f.length(), type)) { /* If the attachment doesn't respect the allowed max file size */
         result.setState(AttachmentItem.STATE_SIZE_ERROR);

      } else if (!checkDuration(f.getAbsolutePath(), type)) { /* If the attachment doesn't respect the allowed max file duration */
         result.setState(AttachmentItem.STATE_DURATION_ERROR);

      } else if (!checkExtension(f.getName(), type)) { /* If the attachment doesn't respect the allowed extensions */
         result.setState(AttachmentItem.STATE_EXTENSION_ERROR);
      }

      /* Delete the temporary file used to store the attachment created with the external application */
      f.delete();

      return result;
   }

   /**
    * Create a new attachment based on the content of the file stored at the passed uri
    *
    * @param activity The activity that manages the file
    * @param uri      The uri of the file picked by the user
    * @param type     of the content stored into the file at the passed uri
    * @return A new attachment object that contains the data of the file stored at the passed uri
    */
   public static AttachmentItem getAttachmentFromUri(Activity activity, Long noteId, Uri uri, String type) {
      /* This AttachmentItem will store the decode of the attachment file associated to the uri */
      AttachmentItem result = new AttachmentItem();
      Bitmap thumbnails = null;

      String tempPath = getPath(uri, activity);
      File f = new File(tempPath);

      /* Check if the file respects the allowed size and an allowed extension */
      if (checkSize(f.length(), type) && checkExtension(f.getName(), type) && checkDuration(f.getAbsolutePath(), type)) {

         /* Create the thumbnails of the attachment based on the type of the attachment */
         switch (type) {
            case AttachmentItem.TYPE_AUDIO:
               thumbnails = BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_action_audio);
               break;
            case AttachmentItem.TYPE_IMAGE:
               thumbnails = AttachmentsAcquisitionManager.decodeSampledBitmapFromFile(f, 50, 50);
               break;
            case AttachmentItem.TYPE_VIDEO:
               thumbnails = ThumbnailUtils.createVideoThumbnail(f.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
               break;
            default:
         }

         /* Initialize a new attachment based on retrieved file */
         result.setName(AttachmentsStoringManager.copyToAppMemory(f, noteId));
         result.setType(type);
         result.setThumbnails(thumbnails);

      } else if (!checkSize(f.length(), type)) { /* If the attachment doesn't respect the allowed max file size */
         result.setState(AttachmentItem.STATE_SIZE_ERROR);

      } else if (!checkDuration(f.getAbsolutePath(), type)) { /* If the attachment doesn't respect the allowed max file duration */
         result.setState(AttachmentItem.STATE_DURATION_ERROR);

      } else if (!checkExtension(f.getName(), type)) { /* If the attachment doesn't respect the allowed extensions */
         result.setState(AttachmentItem.STATE_EXTENSION_ERROR);
      }

      return result;
   }

    /* ********************************************************************************************
     *                              METHODS TO MANAGE THUMBNAILS                                  *
     **********************************************************************************************/

   /**
    * Calculate the in sample size for a bitmap image
    *
    * @param options   The options used to calculate the size
    * @param reqWidth  The raw width for the bitmap image
    * @param reqHeight The raw height for the bitmap image
    * @return The in sample size for a bitmap image
    */
   private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
      /* Raw height and width of image */
      final int height = options.outHeight;
      final int width = options.outWidth;
      int inSampleSize = 1;

      if (height > reqHeight || width > reqWidth) {
         final int halfHeight = height / 2;
         final int halfWidth = width / 2;

         /* Calculate the largest inSampleSize value that is a power of 2 and keeps both
            height and width larger than the requested height and width */
         while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
         }
      }

      return inSampleSize;
   }

   /**
    * Decode a bitmap image from a file using efficiently store parameters
    *
    * @param res       The file that contains the image to decode
    * @param reqWidth  The request width for the result image
    * @param reqHeight The request height for the result image
    * @return The bitmap object associated to the file passed as parameter
    */
   private static Bitmap decodeSampledBitmapFromFile(File res, int reqWidth, int reqHeight) {
      /* First decode with inJustDecodeBounds=true to check dimensions */
      final BitmapFactory.Options options = new BitmapFactory.Options();
      options.inJustDecodeBounds = true;
      BitmapFactory.decodeFile(res.getAbsolutePath(), options);

      /* Calculate inSampleSize */
      options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

      /* Decode bitmap with inSampleSize set */
      options.inJustDecodeBounds = false;
      return BitmapFactory.decodeFile(res.getAbsolutePath(), options);
   }

}